﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using RilattackLibrary;
using System.ServiceModel;
using System.Threading;
using System.Timers;

namespace RilattackServer
{

    [ServiceBehavior(
        InstanceContextMode = InstanceContextMode.Single,
        ConcurrencyMode = ConcurrencyMode.Reentrant)]
    public sealed class Server : IServer
    {
		struct Vect2
		{
			public int x;
			public int y;
		};

		private List<ICallBack> lClientsToInit = new List<ICallBack>();
		// stores update requests on player position, stats, etc and will be poped out upon validation on invalidation on server update
		private Dictionary<ICallBack, Vect2> dUpdateInfo = new Dictionary<ICallBack, Vect2>();

		private int maxDistAllowed = 9999;
		private bool gameStarted = false;
		private Thread updateThread;

		public bool GameStarted
		{
			get
			{
				return gameStarted;
			}

			set
			{
				if(value == true)
				{
					updateThread.Start();
					Console.WriteLine("Update Thread Started.");
				}
				gameStarted = value;
			}
		}

		Server()
		{
			App.ServerInstance = this;
			Console.WriteLine("Serveur instantiated.");
			updateThread = new Thread(new ThreadStart(update));
		}

		public bool login(string pseudo)
        {
            Console.WriteLine("Player " + pseudo + " has logged in.");
            App.PlayerCallBackMap = App.PlayerCallBackMap ?? new Dictionary<ICallBack,Player>();

            if (App.PlayerCallBackMap.Values.Any(x => x.Nickname == pseudo))
            {
                return false;
            }
            ICallBack client = OperationContext.Current.GetCallbackChannel<ICallBack>();
            if (!App.PlayerCallBackMap.ContainsKey(client))
            {
                Player pl = new Player(/*Nickname = pseudo*/);
                pl.Nickname = pseudo;
                App.PlayerCallBackMap.Add(client, pl);

				// UI
                App.MainView.playersView.ItemsSource = App.PlayerCallBackMap.Values;
                App.MainView.playersView.Items.Refresh();
            }
            return true;
        }

        public void OnMessageReceived(ServerMessage message)
        {
            throw new System.NotImplementedException();
        }

		/// <summary>
		/// Gets UID of current callback channel when used through Client interface
		/// </summary>
		/// <returns></returns>
        public Guid getMyUID()
        {
            Player p = null;
            App.PlayerCallBackMap.TryGetValue(OperationContext.Current.GetCallbackChannel<ICallBack>(), out p);
            return p.UID;
        }


        public void CallForInit()
        {
            if (!App.PlayerCallBackMap.ContainsKey(OperationContext.Current.GetCallbackChannel<ICallBack>()))
			{
				return ;
			}
			else
			{
				lClientsToInit.Add(OperationContext.Current.GetCallbackChannel<ICallBack>());
				return ;
			}
        }
		/// <summary>
		/// Log a request for update on a player 
		/// Todo, implement other parameters (than position)
		/// </summary>
		/// <param name="p"></param>
        public void RequestUpdate(int posX, int posY)
        {
			// TODO set other properties 
			Vect2 point;
			point.x = posX;
			point.y = posY;
            lock(dUpdateInfo)
            {
                // drop this request if the last one is still to execute then wait for next 
                if (!dUpdateInfo.ContainsKey(OperationContext.Current.GetCallbackChannel<ICallBack>()))
                {
                    dUpdateInfo.Add(OperationContext.Current.GetCallbackChannel<ICallBack>(), point);
                }
                else
                {
                    dUpdateInfo.Remove(OperationContext.Current.GetCallbackChannel<ICallBack>());
                    dUpdateInfo.Add(OperationContext.Current.GetCallbackChannel<ICallBack>(), point);
                }
            }


		}

		private void update()
		{
			bool once = false;
			int tickRate = 10;
			// infinite loop of server
			while (GameStarted)
			{
				// we work on one at a time, todo could spawn child threads
				if (lClientsToInit.Count != 0)
				{
					Console.WriteLine("Client to initialize: " + lClientsToInit.Count);
					ICallBack iCallB = lClientsToInit[0];
					if (iCallB == null)
						continue;

					popPlayers(iCallB);
					Console.WriteLine("Client to initialize: "+lClientsToInit.Count);
				}
				if (!once)
				{
					// tickrate to update clients
					System.Timers.Timer aTimer = new System.Timers.Timer();
					aTimer.Elapsed += new ElapsedEventHandler(updateClients);
					aTimer.Interval = tickRate;
					aTimer.Enabled = true;
					once = true;
				}

			}
		}

		private void updateClients(object source, ElapsedEventArgs e)
		{
			foreach (KeyValuePair<ICallBack, Player> entry in App.PlayerCallBackMap)
			{
                lock (dUpdateInfo)
                {
                    // updates values of players objects
                    if (dUpdateInfo.ContainsKey(entry.Key))
                    {
                        Vect2 point;
                        point = dUpdateInfo[entry.Key];
                        // If there was movement, apply it (if valid)
                        // todo check other infos
                        // todo do the math only if new =/= old
                        entry.Value.SetPosition(point.x, point.y); // debug
                        if (validateDeltaPos(entry.Value, point))
                        {
                            //entry.Value.SetPosition(point.x, point.y);
                        }
                    }
                }

			}
            // send updated values to each client
            // todo send only =/=
            foreach (KeyValuePair<ICallBack, Player> entry in App.PlayerCallBackMap)
            {
                int x, y;
                entry.Value.GetPosition(out x, out y);
                //Console.WriteLine("Sending RPC to " + entry.Value.Nickname + " for pos " + x + " " + y);

                foreach (KeyValuePair<ICallBack, Player> reciver in App.PlayerCallBackMap.Where(compare => compare.Key != entry.Key))
                {
					reciver.Key.MovePlayer(entry.Value.Nickname, x, y);
                }
            }
        }

		/// <summary>
		/// Todo should receive a full player in place of Vect2 as 2nd arg to validate and update life etc
		/// </summary>
		/// <param name="oP"></param>
		/// <param name="nP"></param>
		/// <returns></returns>
		private bool validateDeltaPos(Player oP, Vect2 nP)
		{
			int oX, oY, nX, nY;
			oP.GetPosition(out oX, out oY);
			nX = nP.x;
			nY = nP.y;

			double a = oX - nX;
			double b = oY - nY;
			double distance = Math.Sqrt(a * a + b * b);
			if(distance < maxDistAllowed)
			{
				return true;
			} 
			else
			{
				return false;
			}				
		}

		private void popPlayers(ICallBack client)
		{
			int x, y;
			foreach (Player p in App.PlayerCallBackMap.Values)
			{
				p.GetPosition(out x, out y);
				if (client != null)
				{
					client.InitPlayer(p.UID, p.Nickname, x, y);
				}
			}
			lClientsToInit.RemoveAt(0);

		}
	}
}